using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using SolanumSolstice_Windows.Units;

namespace SolanumSolstice_Windows.Mapping
{
    public class Map
    {
        private Random rand;
        private Texture2D town;
        private Texture2D alphaMap;
        public static Tile[,] tiles;
        private static List<Tile> walkableTiles;
        private static int mapSizeX;
        private static int mapSizeY;
        private static int focusX;
        private static int focusY;
        private int screenSizeX;
        private int screenSizeY;
        private int mapMoveSpeed;
        private int hudHeight;

        //For PathFinding
        private static List<PathNode> openList;
        private static List<PathNode> closedList;
        private static PathNode currentNode;

        #region Accessors
        public int FocusX
        {
            get
            {
                return focusX;
            }
            set
            {
                focusX = value;
            }
        }

        public int FocusY
        {
            get
            {
                return focusY;
            }
            set
            {
                focusY = value;
            }
        }

        public int MapSizeX
        {
            get
            {
                return mapSizeX;
            }
            set
            {
                mapSizeX = value;
            }
        }

        public int MapSizeY
        {
            get
            {
                return mapSizeY;
            }
            set
            {
                mapSizeY = value;
            }
        }

        public int ScreenSizeX
        {
            get
            {
                return screenSizeX;
            }
            set
            {
                screenSizeX = value;
            }
        }

        public int ScreenSizeY
        {
            get
            {
                return screenSizeY;
            }
            set
            {
                screenSizeY = value;
            }
        }

        public Tile[,] Tiles
        {
            get
            {
                return tiles;
            }
            set
            {
                tiles = value;
            }
        }

        public int MapMoveSpeed
        {
            get
            {
                return mapMoveSpeed;
            }
            set
            {
                mapMoveSpeed = value;
            }
        }

        public int HUDHeight
        {
            get
            {
                return hudHeight;
            }
            set
            {
                hudHeight = value;
            }
        }

        #endregion

        public Map(Texture2D townBackground, Texture2D alphaMap, Texture2D blankTile, Vector2 screenSize)
        {
            rand = new Random();
            town = townBackground;
            this.alphaMap = alphaMap;
            mapSizeX = town.Width;
            mapSizeY = town.Height;
            focusX = 0;
            focusY = 0;
            hudHeight = 150;
            this.screenSizeX = (int)screenSize.X;
            this.screenSizeY = (int)screenSize.Y - hudHeight;
            walkableTiles = new List<Tile>();
            this.mapMoveSpeed = 5;
            CreateTileArray(blankTile);
        }

        public void makeVisible(int x, int y, int xwidth, int ywidth)
        {
            for (int i = 0; i < xwidth; i++)
            {
                for (int j = 0; j < ywidth; j++)
                {

                    Tiles[x + i, y + j].IsVisible = true;
                }
            }
        }


        public void DrawTown(SpriteBatch spritebatch)
        {
            spritebatch.Draw(town, new Vector2(0, 0),
                new Rectangle(focusX, focusY, screenSizeX, screenSizeY), Color.White);

            
        }
        
        public static void setAllVisible()
        {
            foreach (Tile tile in tiles)
            {
                tile.IsVisible = true;
            }
        }

        public void DrawTiles(SpriteBatch spritebatch)
        {
            foreach (Tile tile in tiles)
            {
                if (tile.Position.X >= focusX - 32
                    && tile.Position.Y >= focusY - 32
                    && tile.Position.X < focusX + screenSizeX
                    && tile.Position.Y < focusY + screenSizeY
                    )
                {
                    tile.Draw(spritebatch, focusX, focusY);
                }
            }
        }

        //get a location's position on the grid, return the upper/left pixel for the grid
        static int gridToPixels(int z)
        {
            return z*32;
        }

        static int pixelsToGrid(int z)
        {
            return z / 32;
        }

        static Vector2 getEndGrid()
        {
            return new Vector2((Map.mapSizeX - 32) / 32, (Map.mapSizeY - 32) / 32);
        }

        //Get a random walkable space near the current location (NOT WORKING YET)
        public static Vector2 getRandomWalkableSpace(Vector2 position, int gradius)
        {
            Random rand1 = new Random();
            /*
//            int endgridx = ((Map.mapSizeX - 32) / 32);
//            int endgridy = ((Map.mapSizeY - 32) / 32);

            int locx = (int)(position.X);
            locx += rand1.Next(gridToPixels(gradius*2))-gridToPixels(gradius);

            if (locx<0) locx=0;
            if (locx > Map.mapSizeX) locx = Map.mapSizeX;
            //if (locx>
            //locx=Math.Max(0,locx/32);
            //locx=Math.Min(locx/32, (Map.mapSizeX - 32) / 32);
           

            int locy = (int) (position.Y+ rand1.Next(gridToPixels(gradius*2))-gridToPixels(gradius));

            if (locy < 0) locx = 0;
            if (locy > Map.mapSizeY) locx = Map.mapSizeY;

            //locy=Math.Max(0,locy/32);
            //locy=Math.Min(locy/32, (Map.mapSizeY - 32) / 32);

            while (tiles[pixelsToGrid(locx), pixelsToGrid(locy)].IsWalkable == false)
            {
                locx = (int) (position.X+ rand1.Next(gradius * 2) - gradius);              
                if (locx < 0) locx = 0;
                if (locx > Map.mapSizeX) locx = Map.mapSizeX;

                locy = (int)(position.Y + rand1.Next(gradius * 2) - gradius);

                if (locy < 0) locx = 0;
                if (locy > Map.mapSizeY) locx = Map.mapSizeY;

            }
             * */

            List<Tile> tilesInRange = new List<Tile>();

            foreach (Tile tile in walkableTiles)
            {

                Vector2 vect = tile.Position - position;
                if (vect.Length() <= gradius)
                {
                    tilesInRange.Add(tile);
                }
            }

            int index = rand1.Next(tilesInRange.Count);


            return tilesInRange[index].Position;
        }

        public static Vector2 getRandomWalkableSpace() {
                Random rand1 = new Random();
            /*
                // -32 to account for zero 
                int endgridx = (pixelsToGrid(Map.mapSizeX - 32));
                int endgridy = (pixelsToGrid(Map.mapSizeY - 32));
                int locx = rand1.Next(endgridx);
                int locy = rand1.Next(endgridy);

                while (tiles[locx,locy].IsWalkable == false) {
                    locx = rand1.Next(endgridx);
                    locy = rand1.Next(endgridy);
                }
             * */

                int randIndex = rand1.Next(walkableTiles.Count);



                return new Vector2(walkableTiles[randIndex].Position.X, walkableTiles[randIndex].Position.Y);
        }

        public static Vector2 GetMapPosition(Vector2 screenPosition)
        {
            return (new Vector2(screenPosition.X + focusX, screenPosition.Y + focusY));
        }

        public void CreateTileArray(Texture2D blankTile)
        {
            

            tiles = new Tile[mapSizeX / Tile.TILEWIDTH, mapSizeY / Tile.TILEHEIGHT];

            for (int i = 0; i < mapSizeX / Tile.TILEWIDTH; i++)
            {
                for (int j = 0; j < mapSizeY / Tile.TILEHEIGHT; j++)
                {
                    /* For Testing Walkable Tiles */
                    Color[] tileData = new Color[32 * 32];

                    alphaMap.GetData(0, new Rectangle(i * Tile.TILEWIDTH, j * Tile.TILEHEIGHT, 32, 32), tileData, 0, 32 * 32);

                    bool clear = false;
                    int alphaAmount = 0;

                    foreach (Color color in tileData)
                    {
                        if (color.A == 0)
                        {
                            alphaAmount += 1;
                        }

                    }

                    if (alphaAmount >= ((32 * 32) * 1 / 3))
                    {
                        clear = true;

                        
                    }

                    tiles[i, j] = new Tile(new Vector2(i * Tile.TILEWIDTH, j * Tile.TILEHEIGHT),
                        blankTile, clear);
                    if(tiles[i,j].IsWalkable)
                    {
                        walkableTiles.Add(tiles[i, j]);
                    }


                }
            }

        }

        public static Path GetShortestPath(Vector2 position, Vector2 targetPosition)
        {
            openList = new List<PathNode>();
            closedList = new List<PathNode>();

            float targetModX = targetPosition.X % 32;
            float targetModY = targetPosition.Y % 32;
            float posModX = position.X % 32;
            float posModY = position.Y % 32;

            targetPosition = new Vector2(targetPosition.X - targetModX, targetPosition.Y - targetModY);
            position = new Vector2(position.X - posModX, position.Y - posModY);

            Vector2 distance = targetPosition - position;

            openList.Add(new PathNode(0, (int)distance.Length(), null, position));

            while(openList.Count > 0 && ListContainsPathNode(closedList, targetPosition) != true)
            {
                OperateNode(targetPosition);
            }


            if (openList.Count == 0)
            {
                return null;
            }
            else
            {
                return new Path(position, targetPosition, closedList);
            }
         

        }

        public static void OperateNode(Vector2 targetPosition)
        {
            int Findex = FindLowestFIndex(openList);

            currentNode = openList[Findex];

            openList.Remove(currentNode);

            closedList.Add(currentNode);

            List<Vector2> valids = GetValidPathNodes(currentNode);

            foreach(Vector2 valid in valids)
            {
                Vector2 GCalc = currentNode.Position - valid;
                Vector2 distanceToTarget = targetPosition - valid;

                if (ListContainsPathNode(openList, valid))
                {
                    int index = ListIndexOfPathNode(openList, valid);
                    if (currentNode.G + (int)GCalc.Length() < openList[index].G)
                    {
                        openList[index].G = currentNode.G + (int)GCalc.Length();
                        openList[index].Parent = currentNode;
                    }

                }
                else
                {
                    openList.Add(new PathNode(currentNode.G + (int)GCalc.Length(), (int)distanceToTarget.Length(), currentNode, valid));
                }
            }
            
        }

        public static int FindLowestFIndex(List<PathNode> list)
        {
            int index = 0;

            for (int i = 0; i < list.Count; i++)
            {
                if (list[i].F <= list[index].F)
                {
                    index = i;
                }
            }

            return index;

        }

        public static List<Vector2> GetValidPathNodes(PathNode currentNode)
        {

            List<Vector2> vectors = new List<Vector2>();
            List<Vector2> valids = new List<Vector2>();

            //Vector2 upperLeft = new Vector2(-32, -32);
            Vector2 up = new Vector2(0, -32);
            //Vector2 upperRight = new Vector2(32, -32);
            Vector2 midLeft = new Vector2(-32, 0);
            Vector2 midRight = new Vector2(32, 0);
            //Vector2 lowerLeft = new Vector2(-32, 32);
            Vector2 down = new Vector2(0, 32);
            //Vector2 lowerRight = new Vector2(32, 32);

            //vectors.Add(upperLeft);
            vectors.Add(up);
            //vectors.Add(upperRight);
            vectors.Add(midLeft);
            vectors.Add(midRight);
            //vectors.Add(lowerLeft);
            vectors.Add(down);
            //vectors.Add(lowerRight);

            //Left Border
            if (currentNode.Position.X <= 0)
            {
                //vectors.Remove(upperLeft);
                vectors.Remove(midLeft);
                //vectors.Remove(lowerLeft);
            }
            //Right Border
            if (currentNode.Position.X >= mapSizeX - Tile.TILEWIDTH)
            {
                //vectors.Remove(upperRight);
                vectors.Remove(midRight);
                //vectors.Remove(lowerRight);
            }
            //Upper Border
            if (currentNode.Position.Y <= 0)
            {
                //vectors.Remove(upperLeft);
                vectors.Remove(up);
                //vectors.Remove(upperRight);
            }
            //Lower Border
            if (currentNode.Position.Y >= mapSizeY - Tile.TILEHEIGHT)
            {
                //vectors.Remove(lowerLeft);
                vectors.Remove(down);
                //vectors.Remove(lowerRight);
            }

            foreach (Vector2 vect in vectors)
            {
                bool closed = false;
                bool notWalkable = false;
                  
                if(ListContainsPathNode(closedList,currentNode.Position + vect))
                {
                    closed = true;
                }

                if (tiles[((int)currentNode.Position.X + (int)vect.X) / Tile.TILEWIDTH, ((int)currentNode.Position.Y + (int)vect.Y) / Tile.TILEHEIGHT].IsWalkable == false)
                {
                    notWalkable = true;
                }

                if(!closed && !notWalkable)
                {
                    valids.Add(currentNode.Position + vect);
                }
            }

            return valids;


        }

        public static bool ListContainsPathNode(List<PathNode> nodes, Vector2 position)
        {

            foreach (PathNode node in nodes)
            {
                if (node.Position == position)
                {
                    return true;
                }
            }

            return false;
        }

        public static int ListIndexOfPathNode(List<PathNode> nodes, Vector2 position)
        {


            foreach (PathNode node in nodes)
            {
                if (node.Position == position)
                {
                    return nodes.IndexOf(node);
                }
            }

            return -1;
        }


    }
}
